package com.ponhu.ea2.goodsCenter.provider.support;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ponhu.ea2.constants.CommonConstant;
import com.ponhu.ea2.goodsCenter.provider.config.GenerateDigitConfig;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.channels.ClosedChannelException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Slf4j
@Component
public class RedisSupportService {

    @Resource
    private RedissonClient redissonClient;
    @Autowired
    private GenerateDigitConfig generateDigitConfig;

    /**
     * 判断关联RBucket是否存在
     *
     * @param redisKey
     * @param vClass
     * @param <V>
     * @return
     */
    public <V> boolean bucketIsExists(String redisKey, Class<V> vClass) {
        try {
            RBucket<V> rBucket = redissonClient.getBucket(redisKey, StringCodec.INSTANCE);
            return rBucket.isExists();
        } catch (Exception e) {
            log.warn("Redis异常，放弃Redisson-RBucket.isExists操作，key={}", redisKey);
            return true;
        }
    }

    /**
     * 向Bucket中存放数据
     *
     * @param redisKey
     * @param value
     * @param timeToLive
     * @param timeUnit
     * @param <V>
     */
    public <V> void bucketSet(String redisKey, V value, long timeToLive, TimeUnit timeUnit) {
        try {
            RBucket<V> rBucket = redissonClient.getBucket(redisKey, StringCodec.INSTANCE);
            rBucket.set(value, timeToLive, timeUnit);
        } catch (Exception e) {
            log.warn("Redis异常，放弃Redisson-RBucket.set操作，key={}", redisKey);
        }
    }

    /**
     * 从Bucket中获取数据
     *
     * @param redisKey
     * @param vClass
     * @param <V>
     * @return
     */
    public <V> V bucketGet(String redisKey, Class<V> vClass) {
        try {
            RBucket<V> rBucket = redissonClient.getBucket(redisKey, StringCodec.INSTANCE);
            return rBucket.get();
        } catch (Exception e) {
            log.warn("Redis异常，放弃Redisson-RBucket.get操作，key={}", redisKey);
            return null;
        }
    }

    /**
     * 获取 Redis 缓存数据
     * 首先从Redis缓存获取数据，
     * Redis中没有再由 supplier 函数提供数据，
     * 最后将数据重新缓存到Redis后返回
     *
     * @param redisKey
     * @param supplier
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T getObjFromRedis(String redisKey, Supplier<T> supplier, Class<T> tClass) {
        return getObjFromRedis(redisKey, supplier, tClass, -1, null);
    }

    /**
     * 获取 Redis 缓存数据
     * 首先从Redis缓存获取数据，
     * Redis中没有再由 supplier 函数提供数据，
     * 最后将数据重新缓存到Redis后返回
     *
     * @param redisKey
     * @param supplier
     * @param tClass
     * @param timeToLive
     * @param timeUnit
     * @param <T>
     * @return
     */
    public <T> T getObjFromRedis(String redisKey, Supplier<T> supplier, Class<T> tClass, long timeToLive, TimeUnit timeUnit) {
        T t = null;
        try {
            RBucket<String> rBucket = redissonClient.getBucket(redisKey, StringCodec.INSTANCE);
            String redisValue = rBucket.get();
            if (StringUtils.isNotBlank(redisValue)) { // 从缓存中拿到直接返回
                return JSON.parseObject(redisValue, tClass);
            }

            t = supplier.get();

            // 将数据库获取的数据重新缓存到redis
            if (timeToLive <= 0) {
                rBucket.set(JSON.toJSONString(t));
            } else {
                rBucket.set(JSON.toJSONString(t), timeToLive, timeUnit);
            }

        } catch (Exception e) {
            // Redis 连接异常
            if (e.getCause() instanceof ClosedChannelException) {
                if (t == null) {
                    t = supplier.get();
                }
            } else { // 其它异常依然抛出
                throw e;
            }
        }
        return t;
    }

    /**
     * 加读写锁的读锁
     *
     * @param key      读写锁key值
     * @param supplier 业务执行函数
     * @param <T>      业务执行返回值
     * @return
     */
    public <T> T wrapWithReadLock(String key, Supplier<T> supplier) {
        return wrapWithReadLock(Arrays.asList(key), supplier);
    }

    /**
     * 加读写锁的读锁
     *
     * @param keys     读写锁key值
     * @param supplier 业务执行函数
     * @param <T>      业务执行返回值
     * @return
     */
    public <T> T wrapWithReadLock(List<String> keys, Supplier<T> supplier) {
        List<RLock> locks = null;
        try {
            locks = keys.stream().map(key -> {
                RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
                // 加读锁
                RLock rLock = readWriteLock.readLock();
                rLock.lock();
                return rLock;
            }).collect(Collectors.toList());
        } catch (Exception e) {
            log.warn("Redis异常，放弃Redisson分布式读锁lock操作，keys={}", keys);
        }

        try {
            //执行业务代码
            return supplier.get();
        } finally {
            if (CollectionUtils.isNotEmpty(locks)) {
                try {
                    //释放锁
                    locks.forEach(rLock -> {
                        // 已锁定 且 拥有锁的是当前线程
                        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                            rLock.unlock();
                        }
                    });
                } catch (Exception e) {
                    log.warn("Redis异常，放弃Redisson分布式读锁unlock操作，keys={}", keys);
                }
            }
        }
    }

    /**
     * 加读写锁的写锁
     *
     * @param key      读写锁key值
     * @param supplier 业务执行函数
     * @param <T>      业务执行返回值
     * @return
     */
    public <T> T wrapWithWriteLock(String key, Supplier<T> supplier) {
        return wrapWithWriteLock(Arrays.asList(key), supplier);
    }

    /**
     * 加读写锁的写锁
     *
     * @param keys     读写锁key值
     * @param supplier 业务执行函数
     * @param <T>      业务执行返回值
     * @return
     */
    public <T> T wrapWithWriteLock(List<String> keys, Supplier<T> supplier) {
        List<RLock> locks = null;
        try {
            locks = keys.stream().map(key -> {
                RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);
                // 加写锁
                RLock rLock = readWriteLock.writeLock();
                rLock.lock();
                return rLock;
            }).collect(Collectors.toList());
        } catch (Exception e) {
            log.warn("Redis异常，放弃Redisson分布式写锁lock操作，keys={}", keys);
        }

        try {
            //执行业务代码
            return supplier.get();
        } finally {
            if (CollectionUtils.isNotEmpty(locks)) {
                try {
                    //释放锁
                    locks.forEach(rLock -> {
                        // 已锁定 且 拥有锁的是当前线程
                        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                            rLock.unlock();
                        }
                    });
                } catch (Exception e) {
                    log.warn("Redis异常，放弃Redisson分布式写锁unlock操作，keys={}", keys);
                }
            }
        }
    }

    /**
     * 懒加载获取货品编码生成的自增数字位数
     *
     * @return
     */
    public Integer getGenerateDigit() {

        LocalDateTime maxTodayTime = LocalDate.now().atTime(LocalTime.MAX);
        long timeToLive = LocalDateTime.now().until(maxTodayTime, ChronoUnit.MILLIS);

        return getObjFromRedis(CommonConstant.GENERATE_DIGIT, () ->
                        generateDigitConfig.getDigitVoList().stream().sorted()
                                .filter(vo -> !vo.getStartDate().isAfter(LocalDate.now())).findFirst().get().getDigit(),
                Integer.class, timeToLive, TimeUnit.MILLISECONDS);
    }

}
